﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Comun;

namespace ProgramacionRedes {
    public enum enuEstadoConexion {
        Conectado,
        NoConectado
    }

    public class EventArgs_Conectado : EventArgs {
        public enuEstadoConexion Conectado { get; set; }

        public EventArgs_Conectado() {
            Conectado = enuEstadoConexion.NoConectado;
        }

        public EventArgs_Conectado(enuEstadoConexion conectado) {
            this.Conectado = conectado;
        }
    }

    public class EventArgs_EnSesion : EventArgs {
        public bool SesionIniciada { get; set; }

        public EventArgs_EnSesion() {
            SesionIniciada = false;
        }

        public EventArgs_EnSesion(bool sesion_iniciada) {
            SesionIniciada = sesion_iniciada;
        }
    }

    public delegate void dlg_Conectado(EventArgs_Conectado conctado);

    public delegate void dlg_Log(object sender, EventArgs_Log e);

    public delegate void dlg_EnSesion(object sender, EventArgs_EnSesion e);

    public class EventArgs_Log : EventArgs {
        private string texto;

        public string Texto {
            get { return texto; }
            set { texto = value; }
        }

        public EventArgs_Log() {
            texto = "";
        }

        public EventArgs_Log(string texto) {
            this.texto = texto;
        }
    }

    public class Cliente {
        public event dlg_Conectado evConectadoChanged;
        public event dlg_Log evLogChanged;
        public event dlg_EnSesion evSesionChanged;

        public string IpLocal { get; set; }
        public int PuertoLocal { get; set; }

        public string IpRemota { get; set; }
        public int PuertoRemoto { get; set; }

        public string Nombre { get; set; }
        public string Clave { get; set; }

        private enuEstadoConexion estadoConexion;
        public enuEstadoConexion EstadoConexion {
            get { return estadoConexion; }
            set {
                if (estadoConexion != value) {
                    estadoConexion = value;
                    dlg_Conectado ec = evConectadoChanged;
                    if (ec != null) {
                        ec(new EventArgs_Conectado(estadoConexion));
                    }
                }
            }
        }

        private bool enSesion;
        public bool EnSesion {
            get { return enSesion; } 
            set {
                if (enSesion != value) {
                    enSesion = value;
                    dlg_EnSesion es = evSesionChanged;
                    if (es != null) {
                        es(this,  new EventArgs_EnSesion(enSesion));
                    }
                }
            } 
        }

        private TcpClient sockectCliente = null;

        public Cliente() {
            EstadoConexion = enuEstadoConexion.NoConectado;
            EnSesion = false;
        }

        public void Iniciar() {
            if (EstadoConexion == enuEstadoConexion.Conectado) {
                Log("Ya conectado");
                throw new Exception("Ya conectado");
            } else {
                Log("Conectando ...");
                IPEndPoint epLocal = new IPEndPoint(IPAddress.Parse(IpLocal), PuertoLocal);
                sockectCliente = new TcpClient(epLocal);
                sockectCliente.BeginConnect(IPAddress.Parse(IpRemota), PuertoRemoto, requestCallback, sockectCliente);
            }
        }

        public void Terminar() {
            if (EstadoConexion == enuEstadoConexion.NoConectado) {
                Log("No conectado");
                throw new Exception("No conectado");
            } else {
                if (EnSesion) {
                    CerrarSesion();
                }
                sockectCliente.Close();
                Log("Conexión cerrada");
                EstadoConexion = enuEstadoConexion.NoConectado;
            }
        }

        private void Log(string texto) {
            if (evLogChanged != null) {
                evLogChanged(this, new EventArgs_Log(texto));
            }
        }

        private void requestCallback(IAsyncResult ar) {
            Log("Conexión aceptada");
            TcpClient s = (TcpClient)ar.AsyncState;

            if (EnviarPing()) {
                Log("Conectado");
                EstadoConexion = enuEstadoConexion.Conectado;
            }
        }
        
        private bool EnviarPing() {
            bool res = false;
            try {
                res = Enviar_General(enuOrden.Ping, new List<string>());
            } catch (Exception exc) {
                Console.WriteLine(exc.Message);
            }
            return res;
        }

        public bool IniciarSesion() {
            if (EstadoConexion == enuEstadoConexion.Conectado) {
                Comando cmd_envio = new Comando();
                cmd_envio.Encabezado = enuHeader.REQ;
                cmd_envio.Orden = enuOrden.Sesion;
                cmd_envio.Parametros.Add(Nombre);
                cmd_envio.Parametros.Add(Clave);

                cmd_envio.Enviar(sockectCliente.GetStream());

                Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

                if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                    if (cmd_recibo.Parametros[0] == "OK") {
                        EnSesion = true;
                    } else if (cmd_recibo.Parametros[0] == "ERROR") {
                        if (cmd_recibo.Parametros.Count > 1) {
                            throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                        } else {
                            throw new Exception("ERROR: Error desconocido en el servidor");
                        }
                    }
                } else {
                    throw new Exception("Respuesta del servidor desconocida");
                }
            } else {
                Log("No conectado");
            }
            return EnSesion;
        }

        public bool CerrarSesion() {
            if (EstadoConexion == enuEstadoConexion.Conectado) {
                Comando cmd_envio = new Comando();

                cmd_envio.Encabezado = enuHeader.REQ;
                cmd_envio.Orden = enuOrden.Cerrar;

                cmd_envio.Enviar(sockectCliente.GetStream());

                Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

                if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                    if (cmd_recibo.Parametros[0] == "OK") {
                        EnSesion = false;
                        Terminar();
                    } else if (cmd_recibo.Parametros[0] == "ERROR") {
                        if (cmd_recibo.Parametros.Count > 1) {
                            throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                        } else {
                            throw new Exception("ERROR: Error desconocido en el servidor");
                        }
                    }
                } else {
                    throw new Exception("Respuesta del servidor desconocida");
                }
            } else {
                Log("No conectado");
            }
            return EnSesion;
        }

        #region COMANDOS

        public List<string> ObtenerAmigosBloqueados() {
            List<string> parametros = new List<string>();
            return Obtener_ListString(enuOrden.AmigosBloqueados, parametros);
        }

        public List<string> ObtenerAmigosNoBloqueados() {
            List<string> parametros = new List<string>();
            return Obtener_ListString(enuOrden.AmigosNoBloqueados, parametros);
        }
 
        public bool AgregarAmigo(string nombre) {
            return Enviar_String(enuOrden.SolicitarAmistad, nombre);
        }

        public bool BloquearAmigo(string nombre) {
            return Enviar_String(enuOrden.BloquearAmigo, nombre);
        }

        public bool DesbloquearAmigo(string nombre) {
            return Enviar_String(enuOrden.DesbloquearAmigo, nombre);
        }

        public List<int> ObtenerEstados(string usuario) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            return Obtener_ListInt(enuOrden.EstadosAmigo, parametros);
        }

        public string ObtenerTextoEstado(string usuario, int id_estado) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            parametros.Add(id_estado.ToString());
            return Obtener_String(enuOrden.TextoEstado, parametros);
        }

        public List<int> ObtenerComentariosEstado(string usuario, int id_estado) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            parametros.Add(id_estado.ToString());
            return Obtener_ListInt(enuOrden.ComentariosEstado, parametros);
        }

        public string ObtenerTextoComentario(string usuario, int id_estado, int id_comentario) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            parametros.Add(id_estado.ToString());
            parametros.Add(id_comentario.ToString());
            return Obtener_String(enuOrden.TextoComentario, parametros);
        }

        public string ObtenerUsuarioQueComenta(string usuario, int id_estado, int id_comentario) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            parametros.Add(id_estado.ToString());
            parametros.Add(id_comentario.ToString());
            return Obtener_String(enuOrden.UsuarioQueComenta, parametros);
        }

        public bool ComentarEstado(string usuario, int id_estado, string texto) {
            List<string> parametros = new List<string>();
            parametros.Add(usuario);
            parametros.Add(id_estado.ToString());
            parametros.Add(texto);
            return Enviar_General(enuOrden.ComentarEstado, parametros);
        }

        #endregion

        #region AUXILIARES

        public bool Enviar_String(enuOrden orden, string valor) {
            bool respuesta = false;
            if (EstadoConexion == enuEstadoConexion.Conectado) {
                if (EnSesion) {
                    Comando cmd_envio = new Comando();

                    cmd_envio.Encabezado = enuHeader.REQ;
                    cmd_envio.Orden = orden;
                    cmd_envio.Parametros.Add(valor);

                    cmd_envio.Enviar(sockectCliente.GetStream());

                    Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

                    if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                        if (cmd_recibo.Parametros[0] == "OK") {
                            respuesta = true;
                        } else if (cmd_recibo.Parametros[0] == "ERROR") {
                            if (cmd_recibo.Parametros.Count > 1) {
                                throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                            } else {
                                throw new Exception("ERROR: Error desconocido en el servidor");
                            }
                        }
                    } else {
                        throw new Exception("Respuesta del servidor desconocida");
                    }
                } else {
                    throw new Exception("Sesión no iniciada");
                }
            } else {
                throw new Exception("No conectado");
            }
            return respuesta;
        }

        public bool Enviar_General(enuOrden orden, List<string> valor) {
            bool respuesta = false;
            if (EstadoConexion == enuEstadoConexion.Conectado) {
                if (EnSesion) {
                    Comando cmd_envio = new Comando();

                    cmd_envio.Encabezado = enuHeader.REQ;
                    cmd_envio.Orden = orden;
                    cmd_envio.Parametros = valor;

                    cmd_envio.Enviar(sockectCliente.GetStream());

                    Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

                    if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                        if (cmd_recibo.Parametros[0] == "OK") {
                            respuesta = true;
                        } else if (cmd_recibo.Parametros[0] == "ERROR") {
                            if (cmd_recibo.Parametros.Count > 1) {
                                throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                            } else {
                                throw new Exception("ERROR: Error desconocido en el servidor");
                            }
                        }
                    } else {
                        throw new Exception("Respuesta del servidor desconocida");
                    }
                } else {
                    throw new Exception("Sesión no iniciada");
                }
            } else {
                throw new Exception("No conectado");
            }
            return respuesta;
        }

        public string Obtener_String(enuOrden orden, List<string> parametros) {
            string res = "";

            Comando cmd_envio = new Comando();

            cmd_envio.Encabezado = enuHeader.REQ;
            cmd_envio.Orden = orden;
            cmd_envio.Parametros = parametros;

            cmd_envio.Enviar(sockectCliente.GetStream());

            Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

            if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                if (cmd_recibo.Parametros[0] == "OK") {
                    res = cmd_recibo.Parametros[1];
                } else if (cmd_recibo.Parametros[0] == "ERROR") {
                    if (cmd_recibo.Parametros.Count > 1) {
                        throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                    } else {
                        throw new Exception("ERROR: Error desconocido en el servidor");
                    }
                }
            } else {
                throw new Exception("Respuesta del servidor desconocida");
            }

            return res;
        }

        public int Obtener_Int(enuOrden orden, List<string> parametros) {
            int res = 0;

            Comando cmd_envio = new Comando();

            cmd_envio.Encabezado = enuHeader.REQ;
            cmd_envio.Orden = orden;
            cmd_envio.Parametros = parametros;

            cmd_envio.Enviar(sockectCliente.GetStream());

            Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

            if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                if (cmd_recibo.Parametros[0] == "OK") {
                    if (!int.TryParse(cmd_recibo.Parametros[1], out res)) {
                        throw new Exception("ERROR: El servidor envió un string como int (valor=" + cmd_recibo.Parametros[1] + ")");
                    }
                } else if (cmd_recibo.Parametros[0] == "ERROR") {
                    if (cmd_recibo.Parametros.Count > 1) {
                        throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                    } else {
                        throw new Exception("ERROR: Error desconocido en el servidor");
                    }
                }
            } else {
                throw new Exception("Respuesta del servidor desconocida");
            }

            return res;
        }

        public List<int> Obtener_ListInt(enuOrden orden, List<string> parametros) {
            List<int> lista = new List<int>();

            Comando cmd_envio = new Comando();

            cmd_envio.Encabezado = enuHeader.REQ;
            cmd_envio.Orden = orden;
            cmd_envio.Parametros = parametros;

            cmd_envio.Enviar(sockectCliente.GetStream());

            Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

            if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                if (cmd_recibo.Parametros[0] == "OK") {
                    for (int i = 1; i < cmd_recibo.Parametros.Count; i++) {
                        int valor;
                        if (!int.TryParse(cmd_recibo.Parametros[i], out valor)) {
                            throw new Exception("ERROR: El servidor envió un string como int (valor=" + cmd_recibo.Parametros[i] + ")");
                        }
                        lista.Add(valor);
                    }
                } else if (cmd_recibo.Parametros[0] == "ERROR") {
                    if (cmd_recibo.Parametros.Count > 1) {
                        throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                    } else {
                        throw new Exception("ERROR: Error desconocido en el servidor");
                    }
                }
            } else {
                throw new Exception("Respuesta del servidor desconocida");
            }
            return lista;
        }

        public List<string> Obtener_ListString(enuOrden orden, List<string> parametros) {
            List<string> lista = new List<string>();

            Comando cmd_envio = new Comando();

            cmd_envio.Encabezado = enuHeader.REQ;
            cmd_envio.Orden = orden;
            cmd_envio.Parametros = parametros;

            cmd_envio.Enviar(sockectCliente.GetStream());

            Comando cmd_recibo = Comando.ObtenerComando(sockectCliente.GetStream());

            if (cmd_recibo.Encabezado == enuHeader.RES && cmd_envio.Orden == cmd_recibo.Orden && cmd_recibo.Parametros.Count > 0) {
                if (cmd_recibo.Parametros[0] == "OK") {
                    for (int i = 1; i < cmd_recibo.Parametros.Count; i++) {
                        lista.Add(cmd_recibo.Parametros[i]);
                    }
                } else if (cmd_recibo.Parametros[0] == "ERROR") {
                    if (cmd_recibo.Parametros.Count > 1) {
                        throw new Exception("ERROR: El servidor respondió: " + cmd_recibo.Parametros[1]);
                    } else {
                        throw new Exception("ERROR: Error desconocido en el servidor");
                    }
                }
            } else {
                throw new Exception("Respuesta del servidor desconocida");
            }
            return lista;
        }
        
        #endregion
    }
}
